﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using WQDSkin.Forms.Internal;

namespace WQDSkin.Forms.Controls
{
    public class WQDTabPanel : Panel
    { 
        private int _numRows = 1;       // Nubmer of row calculated in measure and used in arrange
        private int _numHeaders = 0;    // Number of headers excluding the collapsed items
        private double _rowHeight = 0;  // Maximum of all headers height
        private double moveOffsetX = 0;    //水平移动
        private double moveOffsetY = 0;    //垂直移动
        private double currentTotalWidth;
        private double currentTotalHeight;
        private bool isOver = false;
      
        private Button btnRightArrow;
        private Button btnLeftArrow;

        private Size layoutSlotSize; //用于垂直计算

        public WQDTabPanel() : base()
        {
        }

        static WQDTabPanel()
        {
            KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(WQDTabPanel), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
            KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(WQDTabPanel), new FrameworkPropertyMetadata(KeyboardNavigationMode.Cycle));
        }
 
        protected override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
        {
            WQDTabControl tc = TemplatedParent as WQDTabControl;
            if (tc != null)
            { 
                object objHeaderPanel = tc.Template.FindName("HeaderPanel", tc);
 
                if (objHeaderPanel != null)
                {
                    Grid headerPanel = (Grid)objHeaderPanel;
                    object objRightArrow = headerPanel.FindName("btnRightArrow");
                    if (objRightArrow != null)
                    {
                        if (btnRightArrow == null)
                        {
                            btnRightArrow = (Button)objRightArrow;
                            btnRightArrow.Click += BtnRightArrow_Click;
                             
                        } 
                    }

                    object objLeftArrow = headerPanel.FindName("btnLeftArrow");
                    if (objLeftArrow != null)
                    {
                        if(btnLeftArrow != null)
                        {

                        }
                        btnLeftArrow = (Button)objLeftArrow;
                        btnLeftArrow.Click += BtnLeftArrow_Click;
                    }
                }
            }
            return base.CreateUIElementCollection(logicalParent);

           
        }

        protected override Size MeasureOverride(Size constraint)
        {
            Size contentSize = new Size();
            Dock tabAlignment = TabStripPlacement;
         
           int _numRows = 1;
            _numHeaders = 0;
            _rowHeight = 0;

            // For top and bottom placement the panel flow its children to calculate the number of rows and
            // desired vertical size
            if (tabAlignment == Dock.Top || tabAlignment == Dock.Bottom)
            {
                int numInCurrentRow = 0;
                double currentRowWidth = 0;
                double maxRowWidth = 0;
                foreach (UIElement child in InternalChildren)
                {
                    if (child.Visibility == Visibility.Collapsed)
                        continue;

                    _numHeaders++;

                    // Helper measures child, and deals with Min, Max, and base Width & Height properties.
                    // Helper returns the size a child needs to take up (DesiredSize or property specified size).
                    child.Measure(constraint);
                    Size childSize = GetDesiredSizeWithoutMargin(child);

                    if (_rowHeight < childSize.Height)
                        _rowHeight = childSize.Height;

                    if ( currentRowWidth + childSize.Width > constraint.Width && numInCurrentRow > 0)
                    {
                        if (this.IsWrap)
                        {
                            if (maxRowWidth < currentRowWidth)
                                maxRowWidth = currentRowWidth;

                            currentRowWidth = childSize.Width;
                            numInCurrentRow = 1;
                            _numRows++;
                        }
                        else
                        {
                            currentRowWidth += childSize.Width;
                        }

                        this.isOver = true;
                    }
                    else
                    {
                        currentRowWidth += childSize.Width;
                        numInCurrentRow++;
                    }
                }

                if (maxRowWidth < currentRowWidth)
                    maxRowWidth = currentRowWidth;

                currentTotalWidth = currentRowWidth;

                contentSize.Height = _rowHeight * _numRows;

                // If we don't have constraint or content wisth is smaller than constraint width then size to content
                if (double.IsInfinity(contentSize.Width) || DoubleUtil.IsNaN(contentSize.Width) || maxRowWidth < constraint.Width)
                    contentSize.Width = maxRowWidth;
                else
                    contentSize.Width = constraint.Width;
            }
            else if (tabAlignment == Dock.Left || tabAlignment == Dock.Right)
            {
                foreach (UIElement child in InternalChildren)
                {
                    if (child.Visibility == Visibility.Collapsed)
                        continue;

                    _numHeaders++;

                    // Helper measures child, and deals with Min, Max, and base Width & Height properties.
                    // Helper returns the size a child needs to take up (DesiredSize or property specified size).
                    child.Measure(constraint);

                    Size childSize = GetDesiredSizeWithoutMargin(child);

                    if (contentSize.Width < childSize.Width)
                        contentSize.Width = childSize.Width;

                    contentSize.Height += childSize.Height;
                }

                if (contentSize.Height > constraint.Height)
                {
                    this.isOver = true;
                }
                 
                this.currentTotalHeight = contentSize.Height;
            }

            // Returns our minimum size & sets DesiredSize.
            return contentSize;
        }
         
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            Dock tabAlignment = TabStripPlacement;

            WQDTabControl tc = TemplatedParent as WQDTabControl;
            if (tc != null)
            {
                tc.isMultiRow = isOver;
            }
             
            if (tabAlignment == Dock.Top || tabAlignment == Dock.Bottom)
            {
                ArrangeHorizontal(arrangeSize, this.IsWrap);
            }
            else if (tabAlignment == Dock.Left || tabAlignment == Dock.Right)
            {
                ArrangeVertical(arrangeSize);
            }
            return arrangeSize;
        }

        protected override Geometry GetLayoutClip(Size layoutSlotSize)
        {
            RectangleGeometry rectangle = new RectangleGeometry();

            Dock tabAlignment = TabStripPlacement;
            if (tabAlignment == Dock.Top || tabAlignment == Dock.Bottom)
            {
                rectangle.Rect = new Rect(0, 0, layoutSlotSize.Width, layoutSlotSize.Height + 2);
            }
            else if (tabAlignment == Dock.Left || tabAlignment == Dock.Right)
            {
                rectangle.Rect = new Rect(0, 0, layoutSlotSize.Width + 2, layoutSlotSize.Height);
            }

            this.layoutSlotSize = layoutSlotSize;
            return rectangle;
        }

        #region Private Methods


        private void BtnLeftArrow_Click(object sender, RoutedEventArgs e)
        {
            Dock tabAlignment = TabStripPlacement;
            double speed;
            if (tabAlignment == Dock.Top || tabAlignment == Dock.Bottom)
            {
                speed = CalcHorizontalOffset(-1);
                if (this.moveOffsetX + speed > 0)
                {
                    this.moveOffsetX = 0;
                }
                else
                {
                    this.moveOffsetX += speed;
                }
            }
            else if (tabAlignment == Dock.Left || tabAlignment == Dock.Right)
            {
                speed = CalcVerticalOffset(-1);
                if (this.moveOffsetY + speed > 0)
                {
                    this.moveOffsetY = 0;
                }
                else
                {
                    this.moveOffsetY += speed;
                }
            }

            base.InvalidateArrange();
        }

        private void BtnRightArrow_Click(object sender, RoutedEventArgs e)
        {
            Dock tabAlignment = TabStripPlacement;
            double speed;
            if (tabAlignment == Dock.Top || tabAlignment == Dock.Bottom)
            {
                speed = CalcHorizontalOffset();
                if (this.moveOffsetX - speed < this.ActualWidth - this.currentTotalWidth)
                {
                    this.moveOffsetX = (int)(this.ActualWidth - this.currentTotalWidth);
                }
                else
                {
                    this.moveOffsetX -= speed;
                }
            }
            else if (tabAlignment == Dock.Left || tabAlignment == Dock.Right)
            {
                speed = CalcVerticalOffset();
                double height = this.layoutSlotSize.Height - this.currentTotalHeight;
                if (this.moveOffsetY - speed < height)
                {
                    this.moveOffsetY = this.layoutSlotSize.Height - this.currentTotalHeight;
                }
                else
                {
                    this.moveOffsetY -= speed;
                }
            }


            base.InvalidateArrange();
        }


        /// <summary>
        /// -1　点左侧按钮 1:点右侧按钮向左移动
        /// </summary>
        /// <param name="direct"></param>
        /// <returns></returns>
        private double CalcHorizontalOffset(int direct = 1)
        {
            UIElement tempElemnet = null; //最近的
            Point point0 = new Point(); 
            int nearestIndex = 0;       //最近的索引
            double x = 0;               //相对父控点的距离
            for (int i = 0; i < InternalChildren.Count; i++)
            {
                UIElement child = InternalChildren[i];
                if (tempElemnet == null)
                {
                    tempElemnet = child;
                }
                else
                {
                    Point point1 = child.TranslatePoint(point0, this);
                    Point point2 = tempElemnet.TranslatePoint(point0, this);
                    if (Math.Abs(point1.X) < Math.Abs(point2.X))
                    {
                        tempElemnet = child;
                        nearestIndex = i;
                        x = point1.X;
                    }
                } 
            }
            
            double width = tempElemnet.RenderSize.Width;

            if (direct == -1) //左边按钮
            {
                if (x < 0)
                {
                    width = Math.Abs(x);
                }
                else
                {
                    //获取前一个控件
                    if (nearestIndex - 1 >= 0)
                    {
                        width = Math.Abs(InternalChildren[nearestIndex - 1].TranslatePoint(point0, this).X);
                    }
                }

            } 
            return width;
        }
        private double CalcVerticalOffset(int direct = 1)
        {
            UIElement tempElemnet = null; //最近的
            Point point0 = new Point();
            int nearestIndex = 0;       //最近的索引
            double y = 0;               //相对父控点的距离
             
            for (int i = 0; i < InternalChildren.Count; i++)
            {
                UIElement child = InternalChildren[i];
                if (tempElemnet == null)
                {
                    tempElemnet = child;
                }
                else
                {
                    Point point1 = child.TranslatePoint(point0, this);
                    Point point2 = tempElemnet.TranslatePoint(point0, this);
                    if (Math.Abs(point1.Y) < Math.Abs(point2.Y))
                    {
                        tempElemnet = child;
                        nearestIndex = i;
                        y = point1.Y;
                    }
                }
            }

            double height = tempElemnet.RenderSize.Height;

            if (direct == -1) //左边按钮
            {
                if (y < 0)
                {
                    height = Math.Abs(y);
                }
                else
                {
                    //获取前一个控件
                    if (nearestIndex - 1 >= 0)
                    {
                        height = Math.Abs(InternalChildren[nearestIndex - 1].TranslatePoint(point0, this).Y);
                    }
                }

            }

            return height;
        }

        private Size GetDesiredSizeWithoutMargin(UIElement element)
        {
            Thickness margin = (Thickness)element.GetValue(MarginProperty);
            Size desiredSizeWithoutMargin = new Size();
            desiredSizeWithoutMargin.Height = Math.Max(0d, element.DesiredSize.Height - margin.Top - margin.Bottom);
            desiredSizeWithoutMargin.Width = Math.Max(0d, element.DesiredSize.Width - margin.Left - margin.Right);
            return desiredSizeWithoutMargin;
        }

        private double[] GetHeadersSize()
        {
            double[] headerSize = new double[_numHeaders];
            int childIndex = 0;
            foreach (UIElement child in InternalChildren)
            {
                if (child.Visibility == Visibility.Collapsed)
                    continue;

                Size childSize = GetDesiredSizeWithoutMargin(child);
                headerSize[childIndex] = childSize.Width;
                childIndex++;
            }
            return headerSize;
        }

        private void ArrangeHorizontal(Size arrangeSize, bool isWrap)
        {
            Dock tabAlignment = TabStripPlacement;
            bool isMultiRow = _numRows > 1;
            
            WQDTabControl tc = TemplatedParent as WQDTabControl;
            
            int activeRow = 0;
            int[] solution = Array.Empty<int>();
            Vector childOffset = new Vector();
            childOffset.X += this.moveOffsetX;

            double[] headerSize = GetHeadersSize();

            // If we have multirows, then calculate the best header distribution
            if (isMultiRow && tc.IsWrap)
            {
                solution = CalculateHeaderDistribution(arrangeSize.Width, headerSize);
                activeRow = GetActiveRow(solution);

                // TabPanel starts to layout children depend on activeRow which should be always on bottom (top)
                // The first row should start from Y = (_numRows - 1 - activeRow) * _rowHeight
                if (tabAlignment == Dock.Top)
                    childOffset.Y = (_numRows - 1 - activeRow) * _rowHeight;

                if (tabAlignment == Dock.Bottom && activeRow != 0)
                    childOffset.Y = (_numRows - activeRow) * _rowHeight;
            }

            int childIndex = 0;
            int separatorIndex = 0;
            foreach (UIElement child in InternalChildren)
            {
                if (child.Visibility == Visibility.Collapsed)
                    continue;

                Thickness margin = (Thickness)child.GetValue(MarginProperty);
                double leftOffset = margin.Left;
                double rightOffset = margin.Right;
                double topOffset = margin.Top;
                double bottomOffset = margin.Bottom;

                bool lastHeaderInRow = isMultiRow && (separatorIndex < solution.Length && solution[separatorIndex] == childIndex || childIndex == _numHeaders - 1);

                //Length left, top, right, bottom;
                Size cellSize = new Size(headerSize[childIndex], _rowHeight);

                // Align the last header in the row; If headers are not aligned directional nav would not work correctly
                if (lastHeaderInRow)
                {
                    cellSize.Width = arrangeSize.Width - childOffset.X;
                }

                child.Arrange(new Rect(childOffset.X, childOffset.Y, cellSize.Width, cellSize.Height));

                Size childSize = cellSize;
                childSize.Height = Math.Max(0d, childSize.Height - topOffset - bottomOffset);
                childSize.Width = Math.Max(0d, childSize.Width - leftOffset - rightOffset);

                // Calculate the offset for the next child
                childOffset.X += cellSize.Width ;
                if (lastHeaderInRow && isWrap)
                {
                    if ((separatorIndex == activeRow && tabAlignment == Dock.Top) ||
                        (separatorIndex == activeRow - 1 && tabAlignment == Dock.Bottom))
                        childOffset.Y = 0d;
                    else
                        childOffset.Y += _rowHeight;

                    childOffset.X = 0d;
                    separatorIndex++;
                }

                childIndex++;
            }
        }

        private void ArrangeVertical(Size arrangeSize)
        { 
            double childOffsetY = 0d;
            childOffsetY += this.moveOffsetY;

            foreach (UIElement child in InternalChildren)
            {
                if (child.Visibility != Visibility.Collapsed)
                {
                    Size childSize = GetDesiredSizeWithoutMargin(child);
                    child.Arrange(new Rect(0, childOffsetY, arrangeSize.Width, childSize.Height));

                    // Calculate the offset for the next child
                    childOffsetY += childSize.Height;
                }
            }

        }
         
        private int GetActiveRow(int[] solution)
        {
            int activeRow = 0;
            int childIndex = 0;
            if (solution.Length > 0)
            {
                foreach (UIElement child in InternalChildren)
                {
                    if (child.Visibility == Visibility.Collapsed)
                        continue;

                    bool isActiveTab = (bool)child.GetValue(Selector.IsSelectedProperty);

                    if (isActiveTab)
                    {
                        return activeRow;
                    }

                    if (activeRow < solution.Length && solution[activeRow] == childIndex)
                    {
                        activeRow++;
                    }

                    childIndex++;
                }
            }

            // If the is no selected element and aligment is Top  - then the active row is the last row 
            if (TabStripPlacement == Dock.Top)
            {
                activeRow = _numRows - 1;
            }

            return activeRow;
        }
          
        private int[] CalculateHeaderDistribution(double rowWidthLimit, double[] headerWidth)
        {
            double bestSolutionMaxRowAverageGap = 0;
            int numHeaders = headerWidth.Length;

            int numSeparators = _numRows - 1;
            double currentRowWidth = 0;
            int numberOfHeadersInCurrentRow = 0;
            double currentAverageGap = 0;
            int[] currentSolution = new int[numSeparators];
            int[] bestSolution = new int[numSeparators];
            int[] rowHeaderCount = new int[_numRows];
            double[] rowWidth = new double[_numRows];
            double[] rowAverageGap = new double[_numRows];
            double[] bestSolutionRowAverageGap = new double[_numRows];

            // Initialize the current state; Do the initial flow of the headers
            int currentRowIndex = 0;

            for (int index = 0; index < numHeaders; index++)
            {
                if (currentRowWidth + headerWidth[index] > rowWidthLimit && numberOfHeadersInCurrentRow > 0)
                { // if we cannot add next header - flow to next row
                    // Store current row before we go to the next
                    rowWidth[currentRowIndex] = currentRowWidth; // Store the current row width
                    rowHeaderCount[currentRowIndex] = numberOfHeadersInCurrentRow; // For each row we store the number os headers inside
                    currentAverageGap = Math.Max(0d, (rowWidthLimit - currentRowWidth) / numberOfHeadersInCurrentRow); // The amout of width that should be added to justify the header
                    rowAverageGap[currentRowIndex] = currentAverageGap;
                    currentSolution[currentRowIndex] = index - 1; // Separator points to the last header in the row
                    if (bestSolutionMaxRowAverageGap < currentAverageGap) // Remember the maximum of all currentAverageGap
                        bestSolutionMaxRowAverageGap = currentAverageGap;

                    // Iterate to next row
                    currentRowIndex++;
                    currentRowWidth = headerWidth[index]; // Accumulate header widths on the same row
                    numberOfHeadersInCurrentRow = 1;
                }
                else
                {
                    currentRowWidth += headerWidth[index]; // Accumulate header widths on the same row
                    // Increase the number of headers only if they are not collapsed (width=0)
                    if (headerWidth[index] != 0)
                        numberOfHeadersInCurrentRow++;
                }
            }

            // If everithing fit in 1 row then exit (no separators needed)
            if (currentRowIndex == 0)
                return Array.Empty<int>();

            // Add the last row
            rowWidth[currentRowIndex] = currentRowWidth;
            rowHeaderCount[currentRowIndex] = numberOfHeadersInCurrentRow;
            currentAverageGap = (rowWidthLimit - currentRowWidth) / numberOfHeadersInCurrentRow;
            rowAverageGap[currentRowIndex] = currentAverageGap;
            if (bestSolutionMaxRowAverageGap < currentAverageGap)
                bestSolutionMaxRowAverageGap = currentAverageGap;

            currentSolution.CopyTo(bestSolution, 0); // Remember the first solution as initial bestSolution
            rowAverageGap.CopyTo(bestSolutionRowAverageGap, 0); // bestSolutionRowAverageGap is used in ArrangeOverride to calculate header sizes

            // Search for the best solution
            // The exit condition if when we cannot move header to the next row 
            while (true)
            {
                // Find the row with maximum AverageGap
                int worstRowIndex = 0; // Keep the row index with maximum AverageGap
                double maxAG = 0;

                for (int i = 0; i < _numRows; i++) // for all rows
                {
                    if (maxAG < rowAverageGap[i])
                    {
                        maxAG = rowAverageGap[i];
                        worstRowIndex = i;
                    }
                }

                // If we are on the first row - cannot move from previous
                if (worstRowIndex == 0)
                    break;

                // From the row with maximum AverageGap we try to move a header from previous row
                int moveToRow = worstRowIndex;
                int moveFromRow = moveToRow - 1;
                int moveHeader = currentSolution[moveFromRow];
                double movedHeaderWidth = headerWidth[moveHeader];

                rowWidth[moveToRow] += movedHeaderWidth;

                // If the moved header cannot fit - exit. We have the best solution already.
                if (rowWidth[moveToRow] > rowWidthLimit)
                    break;

                // If header is moved successfully to the worst row
                // we update the arrays keeping the row state
                currentSolution[moveFromRow]--;
                rowHeaderCount[moveToRow]++;
                rowWidth[moveFromRow] -= movedHeaderWidth;
                rowHeaderCount[moveFromRow]--;
                rowAverageGap[moveFromRow] = (rowWidthLimit - rowWidth[moveFromRow]) / rowHeaderCount[moveFromRow];
                rowAverageGap[moveToRow] = (rowWidthLimit - rowWidth[moveToRow]) / rowHeaderCount[moveToRow];

                // EvaluateSolution:
                // If the current solution is better than bestSolution - keep it in bestSolution
                maxAG = 0;
                for (int i = 0; i < _numRows; i++) // for all rows
                {
                    if (maxAG < rowAverageGap[i])
                    {
                        maxAG = rowAverageGap[i];
                    }
                }

                if (maxAG < bestSolutionMaxRowAverageGap)
                {
                    bestSolutionMaxRowAverageGap = maxAG;
                    currentSolution.CopyTo(bestSolution, 0);
                    rowAverageGap.CopyTo(bestSolutionRowAverageGap, 0);
                }
            }

            // Each header size should be increased so headers in the row stretch to fit the row
            currentRowIndex = 0;
            for (int index = 0; index < numHeaders; index++)
            {
                headerWidth[index] += bestSolutionRowAverageGap[currentRowIndex];
                if (currentRowIndex < numSeparators && bestSolution[currentRowIndex] == index)
                    currentRowIndex++;
            }
            // Use the best solution bestSolution[0..numSeparators-1] to layout
            return bestSolution;
        }

        private Dock TabStripPlacement
        {
            get
            {
                Dock placement = Dock.Top;
                WQDTabControl tc = TemplatedParent as WQDTabControl;
                if (tc != null)
                    placement = tc.TabStripPlacement;
                return placement;
            }
        }

        private bool IsWrap
        {
            get
            {
                bool isWrap = false; //不换行

                WQDTabControl tc = TemplatedParent as WQDTabControl;
                if (tc != null)
                    isWrap = tc.IsWrap;
                return isWrap;
            }
        }
         

        #endregion


    }
}
